//============================================================================
// Name        : GA.cpp
// Author      : Andrey Bicalho Santos e Clayson Sandro Celes
// Version     :
// Copyright   : if you are making money with it... please give me some, because I have no money!
// Description : Genetic Algorithm in C++, Ansi-style
//============================================================================

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <cstring>
#include "GA.h"
#include "ensemble_classifiers.h"

using namespace std;

typedef struct{
	GAResult config1,config2,config3,config4,config5;
}Configuracao;

GAResult Ensemble_classifiers_problem(double **data_config_training_c1, double **data_config_training_c2, double **data_config_training_c3,
		Dimension dim_train_sets,int number_genes,int number_individuals,int number_ages,int c_rateX,int c_rateY,int m_rateX,int m_rateY,int k_tournament,
		int k_elitism,int max_min_type,int criterio_convergencia);


Dimension dim_full_data;
Dimension dim_data_training;
Dimension dim_data_test;
//-------------------------------------------------------------------------------------
int main(int argc, char** argv){

	int number_genes,number_individuals,number_ages,c_rateX,c_rateY,m_rateX,m_rateY,
		k_tournament,k_elitism,max_min_type,criterio_convergencia;

	string path_file_c1, path_file_c2, path_file_c3;

	if(argc < 2){
		cout << "Insira arquivo Classificador 1: (ex: nome_do_arquivo.txt)" << endl; cin >> path_file_c1;
		cout << "Insira arquivo Classificador 2: (ex: nome_do_arquivo.txt)" << endl; cin >> path_file_c2;
		cout << "Insira arquivo Classificador 3: (ex: nome_do_arquivo.txt)" << endl; cin >> path_file_c3;
		cout << "\nInsira o numero de genes: " << endl; cin >> number_genes;
		cout << "\nInsira o numero de individuos; " << endl; cin >> number_individuals;
		cout << "\nInsira o numero de geracoes: " << endl; cin >> number_ages;
		cout << "\nInsira parametro 1 da taxa de crossover: " << endl; cin >> c_rateX;
		cout << "\nInsira parametro 2 da taxa de crossover: " << endl; cin >> c_rateY;
		cout << "\nInsira parametro 1 da taxa de mutacao: " << endl; cin >> m_rateX;
		cout << "\nInsira parametro 2 da taxa de mutacao: " << endl; cin >> m_rateY;
		cout << "\nInsira parametro K do torneio: " << endl; cin >> k_tournament;
		cout << "\nInsira parametro K do elitismo: " << endl; cin >> k_elitism;
		cout << "\nInsira o tipo de problema (1- maximization e 0- minimization): " << endl; cin >> max_min_type;
		cout << "\nInsira o criterio de convergencia: " << endl; cin >> criterio_convergencia;
	}
	else{
		number_genes = atoi(argv[1]);
		number_individuals = atoi(argv[2]);
		number_ages = atoi(argv[3]);
		c_rateX = atoi(argv[4]);
		c_rateY = atoi(argv[5]);
		m_rateX = atoi(argv[6]);
		m_rateY = atoi(argv[7]);
		k_tournament = atoi(argv[8]);
		k_elitism = atoi(argv[9]);
		max_min_type = atoi(argv[10]);
		criterio_convergencia = atoi(argv[11]);
		path_file_c1 = argv[12];
		path_file_c2 = argv[13];
		path_file_c3 = argv[14];
	}


	double **data_full_c1 = load_full_file(path_file_c1.c_str(), &dim_full_data);
	double **data_full_c2 = load_full_file(path_file_c2.c_str(), &dim_full_data);
	double **data_full_c3 = load_full_file(path_file_c3.c_str(), &dim_full_data);


	double **data_config_training_c1;
	double **data_config_training_c2;
	double **data_config_training_c3;

	double **data_config_test_c1;
	double **data_config_test_c2;
	double **data_config_test_c3;

	//-------------------------------------------------------------------------------------
	//training

	GAResult ensemble_classifiers;
	Configuracao Seed1;

	//configuracao 1
		//training fould 1
		data_config_training_c1 = getDataConfig1Training(data_full_c1, &dim_data_training, &dim_full_data);
		data_config_training_c2 = getDataConfig1Training(data_full_c2, &dim_data_training, &dim_full_data);
		data_config_training_c3 = getDataConfig1Training(data_full_c3, &dim_data_training, &dim_full_data);

		ensemble_classifiers = Ensemble_classifiers_problem(data_config_training_c1, data_config_training_c2, data_config_training_c3,
			dim_data_training, number_genes,number_individuals,number_ages,c_rateX,c_rateY,m_rateX,m_rateY,
			k_tournament,k_elitism,max_min_type,criterio_convergencia);

		Seed1.config1 = ensemble_classifiers;

		printf("\n\nConfiguration 1 - Training set");
		printf("\n\tWeights: (%d,%d,%d) \n\tOverall Accuracy: %f\n",Seed1.config1.weight1,Seed1.config1.weight2,Seed1.config1.weight3,Seed1.config1.oa);



		//configuracao 2
		//training fould 2
		data_config_training_c1 = getDataConfig2Training(data_full_c1, &dim_data_training, &dim_full_data);
		data_config_training_c2 = getDataConfig2Training(data_full_c2, &dim_data_training, &dim_full_data);
		data_config_training_c3 = getDataConfig2Training(data_full_c3, &dim_data_training, &dim_full_data);

		ensemble_classifiers = Ensemble_classifiers_problem(data_config_training_c1, data_config_training_c2, data_config_training_c3,
					dim_data_training,number_genes,number_individuals,number_ages,c_rateX,c_rateY,m_rateX,m_rateY,
					k_tournament,k_elitism,max_min_type,criterio_convergencia);

		Seed1.config2 = ensemble_classifiers;



	//configuracao 3
	//training fould 3
	data_config_training_c1 = getDataConfig3Training(data_full_c1,&dim_data_training, &dim_full_data);
	data_config_training_c2 = getDataConfig3Training(data_full_c2,&dim_data_training, &dim_full_data);
	data_config_training_c3 = getDataConfig3Training(data_full_c3,&dim_data_training, &dim_full_data);

	ensemble_classifiers = Ensemble_classifiers_problem(data_config_training_c1, data_config_training_c2,
			data_config_training_c3,dim_data_training, number_genes, number_individuals, number_ages,c_rateX,c_rateY,
			m_rateX, m_rateY, k_tournament, k_elitism, max_min_type,
			criterio_convergencia);

	Seed1.config3 = ensemble_classifiers;


	//configuracao 4
	//training fould 4
	data_config_training_c1 = getDataConfig4Training(data_full_c1,&dim_data_training, &dim_full_data);
	data_config_training_c2 = getDataConfig4Training(data_full_c2,&dim_data_training, &dim_full_data);
	data_config_training_c3 = getDataConfig4Training(data_full_c3,&dim_data_training, &dim_full_data);

	ensemble_classifiers = Ensemble_classifiers_problem(data_config_training_c1, data_config_training_c2,
			data_config_training_c3,dim_data_training, number_genes, number_individuals, number_ages,c_rateX,c_rateY,
			m_rateX, m_rateY, k_tournament, k_elitism, max_min_type,
			criterio_convergencia);

	Seed1.config4 = ensemble_classifiers;



	//configuracao 5
	//training fould 5
	data_config_training_c1 = getDataConfig5Training(data_full_c1,&dim_data_training, &dim_full_data);
	data_config_training_c2 = getDataConfig5Training(data_full_c2,&dim_data_training, &dim_full_data);
	data_config_training_c3 = getDataConfig5Training(data_full_c3,&dim_data_training, &dim_full_data);

	ensemble_classifiers = Ensemble_classifiers_problem(data_config_training_c1, data_config_training_c2,
			data_config_training_c3,dim_data_training, number_genes, number_individuals, number_ages,c_rateX,c_rateY,
			m_rateX, m_rateY, k_tournament, k_elitism, max_min_type,
			criterio_convergencia);

	Seed1.config5 = ensemble_classifiers;

	//-------------------------------------------------------------------------------------
	//printing training

	double soma = Seed1.config1.oa + Seed1.config2.oa + Seed1.config3.oa + Seed1.config4.oa + Seed1.config5.oa;
	printf("\n\n\n--- Training Phase ---");
	printf("\n\nConfig1 OA: %f  Config2 OA: %f  Config3 OA: %f  Config4 OA: %f  Config5 OA: %f  Average OA: %f",Seed1.config1.oa,Seed1.config2.oa,Seed1.config3.oa,Seed1.config4.oa,Seed1.config5.oa,soma/5);
	int sum_weight1 = Seed1.config1.weight1+Seed1.config2.weight1+Seed1.config3.weight1+Seed1.config4.weight1+Seed1.config5.weight1,
		sum_weight2 = Seed1.config1.weight2+Seed1.config2.weight2+Seed1.config3.weight2+Seed1.config4.weight2+Seed1.config5.weight2,
		sum_weight3 = Seed1.config1.weight3+Seed1.config2.weight3+Seed1.config3.weight3+Seed1.config4.weight3+Seed1.config5.weight3;
	printf("\nConfig1 Weights: (%d,%d,%d)  Config2 Weights: (%d,%d,%d)  Config3 Weights: (%d,%d,%d)  Config4 Weights: (%d,%d,%d)  Config5 Weights: (%d,%d,%d)  \nAverage Weights: (%d,%d,%d)",
			Seed1.config1.weight1,Seed1.config1.weight2,Seed1.config1.weight3,
			Seed1.config2.weight1,Seed1.config2.weight2,Seed1.config2.weight3,
			Seed1.config3.weight1,Seed1.config3.weight2,Seed1.config3.weight3,
			Seed1.config4.weight1,Seed1.config4.weight2,Seed1.config4.weight3,
			Seed1.config5.weight1,Seed1.config5.weight2,Seed1.config5.weight3,
			sum_weight1/5,sum_weight2/5,sum_weight3/5);


    //-------------------------------------------------------------------------------------
	//testing
	int *pesos;
	pesos = new int[3];

	//testing fould 1
	data_config_test_c1 = getDataConfig1Test(data_full_c1, &dim_data_test);
	data_config_test_c2 = getDataConfig1Test(data_full_c2, &dim_data_test);
	data_config_test_c3 = getDataConfig1Test(data_full_c3, &dim_data_test);

	pesos[0] = Seed1.config1.weight1;
	pesos[1] = Seed1.config1.weight2;
	pesos[2] = Seed1.config1.weight3;
	double OA_testing_fould1 = get_predict_OA(data_config_test_c1,data_config_test_c2,data_config_test_c3,dim_data_test,pesos);

	for(int i=0;i<dim_data_test.x;i++){
		delete[] data_config_test_c1[i];
		delete[] data_config_test_c2[i];
		delete[] data_config_test_c3[i];
	}
	delete[] data_config_test_c1;
	delete[] data_config_test_c2;
	delete[] data_config_test_c3;


	//testing fould 2
	data_config_test_c1 = getDataConfig2Test(data_full_c1, &dim_data_test);
	data_config_test_c2 = getDataConfig2Test(data_full_c2, &dim_data_test);
	data_config_test_c3 = getDataConfig2Test(data_full_c3, &dim_data_test);

	pesos[0] = Seed1.config2.weight1;
	pesos[1] = Seed1.config2.weight2;
	pesos[2] = Seed1.config2.weight3;
	double OA_testing_fould2 = get_predict_OA(data_config_test_c1,data_config_test_c2,data_config_test_c3,dim_data_test,pesos);

	for(int i=0;i<dim_data_test.x;i++){
		delete[] data_config_test_c1[i];
		delete[] data_config_test_c2[i];
		delete[] data_config_test_c3[i];
	}
	delete[] data_config_test_c1;
	delete[] data_config_test_c2;
	delete[] data_config_test_c3;

	//testing fould 3
	data_config_test_c1 = getDataConfig3Test(data_full_c1, &dim_data_test);
	data_config_test_c2 = getDataConfig3Test(data_full_c2, &dim_data_test);
	data_config_test_c3 = getDataConfig3Test(data_full_c3, &dim_data_test);

	pesos[0] = Seed1.config3.weight1;
	pesos[1] = Seed1.config3.weight2;
	pesos[2] = Seed1.config3.weight3;
	double OA_testing_fould3 = get_predict_OA(data_config_test_c1,data_config_test_c2,data_config_test_c3,dim_data_test,pesos);

	for(int i=0;i<dim_data_test.x;i++){
		delete[] data_config_test_c1[i];
		delete[] data_config_test_c2[i];
		delete[] data_config_test_c3[i];
	}
	delete[] data_config_test_c1;
	delete[] data_config_test_c2;
	delete[] data_config_test_c3;

	//testing fould 4
	data_config_test_c1 = getDataConfig4Test(data_full_c1, &dim_data_test);
	data_config_test_c2 = getDataConfig4Test(data_full_c2, &dim_data_test);
	data_config_test_c3 = getDataConfig4Test(data_full_c3, &dim_data_test);

	pesos[0] = Seed1.config4.weight1;
	pesos[1] = Seed1.config4.weight2;
	pesos[2] = Seed1.config4.weight3;
	double OA_testing_fould4 = get_predict_OA(data_config_test_c1,data_config_test_c2,data_config_test_c3,dim_data_test,pesos);

	for(int i=0;i<dim_data_test.x;i++){
		delete[] data_config_test_c1[i];
		delete[] data_config_test_c2[i];
		delete[] data_config_test_c3[i];
	}
	delete[] data_config_test_c1;
	delete[] data_config_test_c2;
	delete[] data_config_test_c3;

	//testing fould 5
	data_config_test_c1 = getDataConfig3Test(data_full_c1, &dim_data_test);
	data_config_test_c2 = getDataConfig3Test(data_full_c2, &dim_data_test);
	data_config_test_c3 = getDataConfig3Test(data_full_c3, &dim_data_test);

	pesos[0] = Seed1.config5.weight1;
	pesos[1] = Seed1.config5.weight2;
	pesos[2] = Seed1.config5.weight3;
	double OA_testing_fould5 = get_predict_OA(data_config_test_c1,data_config_test_c2,data_config_test_c3,dim_data_test,pesos);

	for(int i=0;i<dim_data_test.x;i++){
		delete[] data_config_test_c1[i];
		delete[] data_config_test_c2[i];
		delete[] data_config_test_c3[i];
	}
	delete[] data_config_test_c1;
	delete[] data_config_test_c2;
	delete[] data_config_test_c3;

	//-------------------------------------------------------------------------------------
	//printing testing

	printf("\n\n\n--- Testing Phase ---");
	double OA_media = (OA_testing_fould1+OA_testing_fould2+OA_testing_fould3+OA_testing_fould4+OA_testing_fould5)/5;
	printf("\nOA testing fold 1: %f  OA testing fold 2: %f  OA testing fold 3: %f  OA testing fold 4: %f  OA testing fold 5: %f  \nOA media: %f",OA_testing_fould1,OA_testing_fould2,
			OA_testing_fould3,OA_testing_fould4,OA_testing_fould5,OA_media);



	//-------------------------------------------------------------------------------------
	//OA usando todos os dados dos datasets e a media dos pesos encontrada usando 5-fold-cross-validation
	int *media_pesos;
	media_pesos = new int[3];
	media_pesos[0] = sum_weight1;
	media_pesos[1] = sum_weight2;
	media_pesos[2] = sum_weight3;

	double OA_ = get_predict_OA(data_full_c1,data_full_c2,data_full_c3,dim_full_data,media_pesos);
	printf("\n\nOA por classificador:");
	printf("\nC1 OA: %f \tC2 OA: %f \tC3 OA: %f",getOA(data_full_c1,dim_full_data),getOA(data_full_c2,dim_full_data),getOA(data_full_c3,dim_full_data));
	printf("\n\nOA usando todos os dados dos datasets e utilizando a media dos pesos encontrada pelo 5-fold-cross-validation\n");
	printf("OA: %f\n\n",OA_);
	//liberando memoria
	delete[] media_pesos;


	//liberando memoria
	for(int i=0;i<dim_full_data.x;i++){
		delete[] data_full_c1[i];
		delete[] data_full_c2[i];
		delete[] data_full_c3[i];
	}
	delete[] data_full_c1;
	delete[] data_full_c2;
	delete[] data_full_c3;

	delete[] pesos;


	cout << "\nit works!\n";
	return 0;
}

//-------------------------------------------------------------------------------------
GAResult Ensemble_classifiers_problem(double **data_config_training_c1, double **data_config_training_c2, double **data_config_training_c3,
		Dimension dim_train_sets, int number_genes,int number_individuals,int number_ages,int c_rateX,int c_rateY,int m_rateX,int m_rateY,int k_tournament,
		int k_elitism,int max_min_type,int criterio_convergencia){

		cout << "Cômite de Classificadores\n\n" << endl;


		GAResult solution_ensemble;

		int index_melhor,index_pior;

		GA *ensembleProblem;
		ensembleProblem = new GA(number_ages,number_genes,number_individuals,c_rateX,c_rateY,
				                 m_rateX,m_rateY,k_tournament,k_elitism,max_min_type);
		Individual *population;
		population = new Individual[number_individuals];
		for(int i=0;i<number_individuals;i++){
			population[i].setNumGenes(number_genes);
			population[i].Initialize();
		}

		int *phenotype;
		do{

			//setFitness to individual
			/*for(int i=0;i<ensembleProblem->getNumIndividuals();i++){
				printf("\nIndividuo %d",i);
				population[i].printIndividual();
				phenotype = get_phenotype(population[i]);
				printf("\nPhenotype: (%d, %d, %d)",phenotype[0],phenotype[1],phenotype[2]); pera();
			}
			delete[] phenotype;
			*/

			//printf("\n\nPopulacao ANTES da fitness:\n");
			//ensembleProblem->printPopulation(population); pera();

			for(int i=0;i<ensembleProblem->getNumIndividuals();i++)
				population[i].setFitness(fitness_ensemble_classifiers(population,i,data_config_training_c1,data_config_training_c2,data_config_training_c3,dim_train_sets));


			//printf("\n\nPopulacao DEPOIS da fitness:\n");
			//ensembleProblem->printPopulation(population); pera();

			//mostra melhor indivíduo
			index_melhor = ensembleProblem->getHighestFitnessIndividualIndex(population);
			printf("\nAge: %d",ensembleProblem->getCurrentAge());
			printf("\tFitness Average: %f",ensembleProblem->getAverageFitness(population));
			printf("\tVariance: %f",ensembleProblem->getVarianceFitness(population));
			printf("\tStandard deviation: %f",ensembleProblem->getStdDevFitness(population));
			printf("\tBest Individual: %d  Fitness: %f  #Genes: %d",index_melhor,population[index_melhor].getFitness(),population[index_melhor].getNumGenes());


			population = ensembleProblem->Run(population);

		}while((ensembleProblem->getCurrentAge() < ensembleProblem->getAge()) );//&& (prisonerProblem->getAverageFitness(population) < (population[prisonerProblem->getHighestFitnessIndividualIndex(population)].getFitness()) - criterio_convergencia));

		for(int i=0;i<ensembleProblem->getNumIndividuals();i++)
			population[i].setFitness(fitness_ensemble_classifiers(population,i,data_config_training_c1,data_config_training_c2,data_config_training_c3,dim_train_sets));

		//Retornando o melhor
		/*
		printf("\n\nGA ended\n");
		index_melhor = ensembleProblem->getHighestFitnessIndividualIndex(population);
		phenotype = get_phenotype(population,index_melhor);
		printf("\nAge: %d",ensembleProblem->getCurrentAge());
		printf("\tFitness Average: %f",ensembleProblem->getAverageFitness(population));
		printf("\tVariance: %f",ensembleProblem->getVarianceFitness(population));
		printf("\tStandard deviation: %f",ensembleProblem->getStdDevFitness(population));
		printf("\nBest Individual: %d  Fitness: %f  #Genes: %d",index_melhor,population[index_melhor].getFitness(),population[index_melhor].getNumGenes());
		printf("\nPhenotype: (%d,%d,%d)   Fitness: %f\n\n",phenotype[0],phenotype[1],phenotype[2],population[index_melhor].getFitness());


		solution_ensemble.oa = population[index_melhor].getFitness();
		solution_ensemble.weight1 = phenotype[0];
		solution_ensemble.weight2 = phenotype[1];
		solution_ensemble.weight3 = phenotype[2];
		 */

		//Retornando o Pior
		printf("\n\nGA ended\n");
		index_pior = ensembleProblem->getLowestFitnessIndividualIndex(population);
		phenotype = get_phenotype(population,index_pior);
		printf("\nAge: %d",ensembleProblem->getCurrentAge());
		printf("\tFitness Average: %f",ensembleProblem->getAverageFitness(population));
		printf("\tVariance: %f",ensembleProblem->getVarianceFitness(population));
		printf("\tStandard deviation: %f",ensembleProblem->getStdDevFitness(population));
		printf("\nWorst Individual: %d  Fitness: %f  #Genes: %d",index_pior,population[index_pior].getFitness(),population[index_pior].getNumGenes());
		printf("\nPhenotype: (%d,%d,%d)   Fitness: %f\n\n",phenotype[0],phenotype[1],phenotype[2],population[index_pior].getFitness());


		solution_ensemble.oa = population[index_pior].getFitness();
		solution_ensemble.weight1 = phenotype[0];
		solution_ensemble.weight2 = phenotype[1];
		solution_ensemble.weight3 = phenotype[2];


		delete[] phenotype;
		for (int i = 0; i < dim_data_training.x; i++) {
			delete[] data_config_training_c1[i];
			delete[] data_config_training_c2[i];
			delete[] data_config_training_c3[i];
		}
		delete[] data_config_training_c1;
		delete[] data_config_training_c2;
		delete[] data_config_training_c3;

	return solution_ensemble;
}

//-------------------------------------------------------------------------------------
